package com.boyadegroot.divelog.data;

import android.util.Log;
import android.util.Xml;

import com.boyadegroot.divelog.models.Dive;
import com.boyadegroot.divelog.models.DiveSample;
import com.boyadegroot.divelog.models.Diver;
import com.boyadegroot.divelog.utilities.DateUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class DiveLogImport {

    private static final String TAG = DiveLogImport.class.getName();
    
    private XmlPullParser mParser = Xml.newPullParser();

    public void readDiveXml(DiveLogData diveLogData, InputStream inputStream, Diver diver, boolean overwriteData, boolean overwriteSamples) throws XmlPullParserException, IOException {
        // Log.d(TAG, "readDiveXml(InputStream inputStream, diver = " + diver + ", overwriteData = " + overwriteData + ", overwriteSamples = " + overwriteSamples  + ", DiveLogData data)");
        Date date = null;
        Date time = null;
        Integer duration = null;
        Integer surfaceInterval = null;
        Integer o2Percentage = null;
        Double weight = null;
        Double avgDepth = null;
        Integer airTemp = null;
        Integer bottomTemp = null;
        Integer surfaceTemp = null;
        String logNotes = null;
        Double maxDepth = null;
        Integer tankVolume = null;
        Integer sampleCount = null;
        List<DiveSample> samples = new ArrayList<>();
        try {
            mParser.setInput(inputStream, null);
            DiveSample currentSample = null;
            boolean inSample = false;
            boolean validSample = false;
            int event = mParser.getEventType();
            while (event != XmlPullParser.END_DOCUMENT) {
                String tagName = mParser.getName();
                if (event == XmlPullParser.START_TAG) {
                    switch (tagName) {
                        case "SAMPLE":
                            currentSample = new DiveSample();
                            inSample = true;
                            validSample = true;
                            break;
                        case "SAMPLETIME":
                            if (inSample) {
                                Integer value = readInteger();
                                if (value == null) {
                                    validSample = false;
                                } else {
                                    currentSample.setSampleTime(value);
                                }
                            }
                            break;
                        case "DEPTH":
                            if (inSample) {
                                Double value = readDouble();
                                if (value == null) {
                                    validSample = false;
                                } else {
                                    currentSample.setDepth(value);
                                }
                            }
                            break;
                        case "PRESSURE":
                            if (inSample) {
                                Integer value = readInteger();
                                if (value == null) {
                                    validSample = false;
                                } else {
                                    currentSample.setPressure(value);
                                }
                            }
                            break;
                        case "TEMPERATURE":
                            if (inSample) {
                                Integer value = readInteger();
                                if (value == null) {
                                    validSample = false;
                                } else {
                                    currentSample.setTemperature(value);
                                }
                            }
                            break;
                        case "SACRATE":
                            if (inSample) {
                                Double value = readDouble();
                                if (value == null) {
                                    validSample = false;
                                } else {
                                    currentSample.setSacRate(value);
                                }
                            }
                            break;
                        case "CYLPRESS":
                            if (inSample) {
                                Integer value = readInteger();
                                if (value == null) {
                                    validSample = false;
                                } else {
                                    currentSample.setCylinderPressure(value);
                                }
                            }
                            break;
                        case "BOOKMARKTYPE":
                            if (inSample) {
                                Integer value = readInteger();
                                if (value != null) {
                                    DiveSample.Bookmark bookmark = DiveSample.Bookmark.fromCode(value);
                                    currentSample.setBookmark(bookmark);
                                }
                            }
                            break;
                        case "BOOKMARK": // only when BOOKMARKTYPE = 150
                            if (inSample) {
                                String value = readText();
                                if (value != null && !value.trim().isEmpty()) {
                                    currentSample.setBookmarkText(value);
                                }
                            }
                            break;
                        case "DATE":
                            date = readDate();
                            break;
                        case "TIME":
                            time = readTime();
                            break;
                        case "DIVETIMESEC":
                            duration = readInteger();
                            break;
                        case "surfaceInterval":
                            surfaceInterval = readInteger();
                            break;
                        case "SAMPLECNT":
                            sampleCount = readInteger();
                            break;
                        case "CYLINDERSIZE":
                            tankVolume = readInteger();
                            break;
                        case "O2PCT":
                            o2Percentage = readInteger();
                            break;
                        case "WEIGHT":
                            weight = readDouble();
                            break;
                        case "LOGNOTES":
                            logNotes = readText();
                            if (logNotes != null) {
                                logNotes = logNotes.replaceAll("\\r|\\n", " ").replace("  ", " ").trim();
                                if (logNotes.isEmpty()) {
                                    logNotes = null;
                                }
                            }
                            break;
                        case "MAXDEPTH":
                            maxDepth = readDouble();
                            break;
                        case "MEANDEPTH":
                            avgDepth = readDouble();
                            break;
                        case "AIRTEMP":
                            airTemp = readInteger();
                            if (airTemp == 0) {
                                airTemp = null;
                            }
                            break;
                        case "WATERTEMPMAXDEPTH":
                            bottomTemp = readInteger();
                            if (bottomTemp == 0) {
                                bottomTemp = null;
                            }
                            break;
                        case "WATERTEMPATEND":
                            surfaceTemp = readInteger();
                            if (surfaceTemp == 0) {
                                surfaceTemp = null;
                            }
                            break;
                    }
                } else if (event == XmlPullParser.END_TAG) {
                    if (tagName.contentEquals("SAMPLE")) {
                        if (validSample) {
                            samples.add(currentSample);
                        } else {
                            Log.w(TAG, "Skipping invalid sample: " + currentSample);
                        }
                        inSample = false;
                    }
                }
                event = mParser.next();
            } // finish parsing the document
            if (date != null && time != null) {
                Long startTime = DateUtils.getTimestamp(date, time);
                Dive dive = diveLogData.getDiveByStartTime(startTime, diver);
                if (dive == null) {
                    // Log.d(TAG, "Creating new dive");
                    dive = new Dive();
                } else {
                    // Log.d(TAG, "Updating existing dive: " + dive);
                    if (sampleCount != samples.size()) {
                        Log.w(TAG, "sampleCount was " + sampleCount + " but " + samples.size() + " samples found");
                    }
                }
                boolean updateDive = false;

                int durationFromDive = dive.getDuration();
                if (duration != null && duration != 0 && (durationFromDive == 0 || (overwriteData && duration != durationFromDive))) {
                    Log.d(TAG, "Updating duration from " + durationFromDive + " to " + duration);
                    updateDive = true;
                } else {
                    duration = null;
                }

                int surfaceIntervalFromDive = dive.getSurfaceInterval();
                if (surfaceInterval != null && surfaceInterval != 0 && (surfaceIntervalFromDive == 0 || (overwriteData && surfaceInterval != surfaceIntervalFromDive))) {
                    Log.d(TAG, "Updating surfaceInterval from " + surfaceIntervalFromDive + " to " + surfaceInterval);
                    updateDive = true;
                } else {
                    surfaceInterval = null;
                }
                int o2PercentageFromDive = dive.getO2Percentage();
                if (o2Percentage != null && o2Percentage != 0 && (o2PercentageFromDive == 0 || (overwriteData && o2Percentage != o2PercentageFromDive))) {
                    Log.d(TAG, "Updating o2Percentage from " + o2PercentageFromDive + " to " + o2Percentage);
                    updateDive = true;
                } else {
                    o2Percentage = null;
                }
                double maxDepthFromDive = dive.getMaxDepth();
                if (maxDepth != null && maxDepth != 0 && (maxDepthFromDive == 0 || (overwriteData && maxDepth != maxDepthFromDive))) {
                    Log.d(TAG, "Updating maxDepth from " + maxDepthFromDive + " to " + maxDepth);
                    updateDive = true;
                } else {
                    maxDepth = null;
                }
                double avgDepthFromDive = dive.getAvgDepth();
                if (avgDepth != null && avgDepth != 0 && (avgDepthFromDive == 0 || (overwriteData && avgDepth != avgDepthFromDive))) {
                    Log.d(TAG, "Updating avgDepth from " + avgDepthFromDive + " to " + avgDepth);
                    updateDive = true;
                } else {
                    avgDepth = null;
                }
                int airTempFromDive = dive.getAirTemp();
                if (airTemp != null && airTemp != 0 && (airTempFromDive == 0 || (overwriteData && airTemp != airTempFromDive))) {
                    Log.d(TAG, "Updating airTemp from " + airTempFromDive + " to " + airTemp);
                    updateDive = true;
                } else {
                    airTemp = null;
                }
                int bottomTempFromDive = dive.getBottomTemp();
                if (bottomTemp != null && bottomTemp != 0 && (bottomTempFromDive == 0 || (overwriteData && bottomTemp != bottomTempFromDive))) {
                    Log.d(TAG, "Updating bottomTemp from " + bottomTempFromDive + " to " + bottomTemp);
                    updateDive = true;
                } else {
                    bottomTemp = null;
                }
                int surfaceTempFromDive = dive.getSurfaceTemp();
                if (surfaceTemp != null && surfaceTemp != 0 && (surfaceTempFromDive == 0 || (overwriteData && surfaceTemp != surfaceTempFromDive))) {
                    Log.d(TAG, "Updating surfaceTemp from " + surfaceTempFromDive + " to " + surfaceTemp);
                    updateDive = true;
                } else {
                    surfaceTemp = null;
                }
                int tankVolumeFromDive = dive.getTankVolume();
                if (tankVolume != null && tankVolume != 0 && (tankVolumeFromDive == 0 || (overwriteData && tankVolume != tankVolumeFromDive))) {
                    Log.d(TAG, "Updating tankVolume from " + tankVolumeFromDive + " to " + tankVolume);
                    updateDive = true;
                } else {
                    tankVolume = null;
                }
                double weightFromDive = dive.getWeight();
                if (weight != null && weight != 0 && (weightFromDive == 0 || (overwriteData && weight != weightFromDive))) {
                    Log.d(TAG, "Updating weight from " + weightFromDive + " to " + weight);
                    updateDive = true;
                } else {
                    weight = null;
                }
                String logNotesFromDive = dive.getLogNotes();
                if (logNotes != null && !logNotes.isEmpty() &&
                        (
                                logNotesFromDive == null ||
                                        logNotesFromDive.isEmpty() ||
                                        (overwriteData && !logNotes.contentEquals(logNotesFromDive))
                        )
                        ) {
                    Log.d(TAG, "Updating logNotes from " + logNotesFromDive + " to " + logNotes);
                    updateDive = true;
                } else {
                    logNotes = null;
                }
                if (updateDive) {
                    diveLogData.updateDive(
                            dive.getId(),
                            duration,
                            surfaceInterval,
                            o2Percentage,
                            maxDepth,
                            avgDepth,
                            airTemp,
                            bottomTemp,
                            surfaceTemp,
                            tankVolume,
                            weight,
                            logNotes
                    );
                }
                int samplesCountFromData = samples.size();
                int samplesCountFromDive = diveLogData.getDiveSamplesCount(dive.getId());
                if (samplesCountFromData > 0 && (samplesCountFromDive == 0 || (overwriteSamples && samplesCountFromDive < samplesCountFromData))) {
                    // Log.d(TAG, "Adding " + samplesCountFromData + " samples (currently " + samplesCountFromDive + ")");
                    diveLogData.insertSamplesForDive(dive.getId(), samples, true);
                }
            }
        } finally {
            inputStream.close();
        }
    }

    private Date readDate() throws IOException, XmlPullParserException {
        // dd.mm.yyyy
        Date date = null;
        String dateText = readText();
        try {
            if (dateText != null) {
                String[] dateParts = dateText.split("\\.");
                int day = Integer.parseInt(dateParts[0]);
                int month = Integer.parseInt(dateParts[1]);
                int year = Integer.parseInt(dateParts[2]);
                date = DateUtils.getDate(year, month, day);
            }
        } catch (Exception e) {
            Log.e(TAG, "Could not parse date: " + dateText);
        }
        return date;
    }
    
    private Date readTime() throws IOException, XmlPullParserException {
        // HH:mm:ss
        Date time = null;
        String timeText = readText();
        try {
            if (timeText != null) {
                String[] timeParts = timeText.split(":");
                int hour = Integer.parseInt(timeParts[0]);
                int minute = Integer.parseInt(timeParts[1]);
                int second = Integer.parseInt(timeParts[2]);
                time = DateUtils.getTime(hour, minute, second);
            }
        } catch (Exception e) {
            Log.e(TAG, "Could not parse time: " + timeText);
        }
        return time;
    }

    private Double readDouble() throws IOException, XmlPullParserException {
        Double dbl = null;
        String dblText = readText();
        try {
            if (dblText != null) {
                NumberFormat format = NumberFormat.getInstance(Locale.FRANCE); // locale with comma as decimal separator
                Number dblNumber = format.parse(dblText);
                dbl = dblNumber.doubleValue();
            }
        } catch (Exception e) {
            Log.e(TAG, "Could not parse: " + dblText);
        }
        return dbl;
    }

    private Integer readInteger() throws IOException, XmlPullParserException {
        Integer integer = null;
        String integerText = readText();
        try {
            if (integerText != null) {
                integer = Integer.parseInt(integerText);
            }
        } catch (Exception e) {
            Log.e(TAG, "Could not parse: " + integerText);
        }
        return integer;
    }

    private String readText() throws IOException, XmlPullParserException {
        String text = null;
        if (mParser.next() == XmlPullParser.TEXT) {
            text = mParser.getText();
            mParser.nextTag();
        }
        return text;
    }
}